#ifndef PILACOMPARTIDA_H_
#define PILACOMPARTIDA_H_

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string>
#include <errno.h>
#include <string.h>
#include <iostream>
#include "Semaforo.h"

template<class T> class PilaCompartida
{
private:
	std::string ruta_archivo;
	int shm_id;
	int cant_elem_max;
	int *indice;
	T *ptr_datos;
	Semaforo liberados;
	Semaforo tomados;
	Semaforo mutex;

protected:
	PilaCompartida();
	int cantidadProcesosAdosados() const;
	key_t generarClave(const std::string & archivo, char letra);
	void log(const std::string & mensaje);

public:

	static void inicializarSemaforos(const PilaCompartida& unaPila)
	{
		unaPila.liberados.setVal(0);
		unaPila.tomados.setVal(unaPila.cant_elem_max);
		unaPila.mutex.setVal(1);
	}

	PilaCompartida(const std::string& ruta_archivo, int cant_elem_max);
	PilaCompartida(const PilaCompartida & orig);
	virtual ~PilaCompartida();
	PilaCompartida<T>& operator=(const PilaCompartida & origen);
	T desapilar();
	void apilar(const T & dato);
	void crear();
};

template<class T> PilaCompartida<T>::PilaCompartida() : shm_id(-1),
cant_elem_max(1), indice(NULL), ptr_datos(NULL)
{
}

template<class T>
PilaCompartida<T>::PilaCompartida(const std::string &ruta_archivo,
int cant_elem_max) : ruta_archivo(ruta_archivo), shm_id(-1),
cant_elem_max(cant_elem_max), indice(NULL), ptr_datos(NULL),
liberados(generarClave(ruta_archivo, 'a')),//, 0),
tomados(generarClave(ruta_archivo, 'b')),//, cant_elem_max),
mutex(generarClave(ruta_archivo, 'c'))//, 1)
{
	if (cant_elem_max <= 0)
		throw std::string("cant_elem_max incorrecta (<=0).");
}

template<class T>
PilaCompartida<T>::PilaCompartida(const PilaCompartida &orig) :
ruta_archivo(orig.ruta_archivo), shm_id(orig.shm_id),
cant_elem_max(orig.cant_elem_max), indice(orig.indice),
liberados(orig.liberados), tomados(orig.tomados), mutex(orig.mutex)
{
	void* tmpPtr = shmat(orig.shm_id, NULL, 0);
	if (tmpPtr != (void*) -1)
		this->ptr_datos = static_cast<T*>(tmpPtr);
	else
		throw std::string("Error en shmat(): ") + strerror(errno);
}

template<class T> PilaCompartida<T>::~PilaCompartida()
{
	int errorDt = shmdt(static_cast<void*>(this->indice));
	if (errorDt != -1)
	{
		int procAdosados = this->cantidadProcesosAdosados();
		if (procAdosados == 0)
		{
			log(std::string("Eliminando la pila."));
			if(shmctl(this->shm_id, IPC_RMID, NULL) != -1) {
				log(std::string("Pila eliminada."));
				log(std::string("Eliminando semáforos."));
				liberados.eliminar();
				tomados.eliminar();
				mutex.eliminar();
				log(std::string("Semáforos eliminados."));
			} else {
				log("Error al eliminar la pila.");
				log(std::string("Error en shmctl(): ") + strerror(errno));
			}
		}
	} else
		log(std::string("Error en shmdt(): ") + strerror(errno));
}

template<class T> T PilaCompartida<T>::desapilar()
{
	this->liberados.p();
	log(std::string("Esperando bloquear la pila."));
	mutex.p();
	log(std::string("Pila bloqueada."));
	*indice = *indice - 1;
	if (*indice < 0)
		throw std::string("Se intento desapilar estando la pila vacia.");
	T dato = ptr_datos[*indice];

	std::cout << "[" << getpid() << "]" << " Dato='" << dato << "' desapilado."	<< std::endl;

	log(std::string("Desbloqueando la pila."));
	mutex.v();
	log(std::string("Pila desbloqueada."));
	tomados.v();
	return dato;
}

template<class T> void PilaCompartida<T>::apilar(const T & dato)
{
	tomados.p();
	log(std::string("Esperando bloquear la pila."));
	mutex.p();
	log(std::string("Pila bloqueada."));
	if (*indice >= cant_elem_max)
		throw std::string("Se intento apilar estando la pila llena.");
	ptr_datos[*indice] = dato;
	*indice = *indice + 1;

	std::cout << "[" << getpid() << "]" << " Dato='" << dato << "' apilado."<< std::endl;

	log(std::string("Desbloqueando la pila."));
	mutex.v();
	log(std::string("Pila desbloqueada."));
	this->liberados.v();
}

template<class T> void PilaCompartida<T>::crear()
{
	shm_id = shmget(generarClave(ruta_archivo, 'a'),
					sizeof(*indice) + sizeof(T) * this->cant_elem_max,
					0644 | IPC_CREAT);
	if (this->shm_id > 0)
	{
		void* tmpPtr = shmat(this->shm_id, NULL, 0);
		if (tmpPtr != (void*) -1)
		{
			this->indice = static_cast<int*>(tmpPtr);
			this->ptr_datos = static_cast<T*>(indice + 1);
		} else
			throw std::string("Error en shmat(): ") + strerror(errno);
	} else
		throw std::string("Error en shmget(): ") + strerror(errno);
}

template<class T>
PilaCompartida<T>& PilaCompartida<T>::operator=(const PilaCompartida& origen)
{
	this->shm_id = origen.shm_id;
	void* tmpPtr = shmat(this->shm_id, NULL, 0);
	if (tmpPtr != (void*) -1)
	{
		this->ruta_archivo = origen.ruta_archivo;
		this->cant_elem_max = origen.cant_elem_max;
		this->indice = static_cast<int*>(tmpPtr);
		this->ptr_datos = static_cast<T*>(indice + 1);
		this->liberados(origen.liberados);
		this->tomados(origen.tomados);
	} else
		throw std::string("Error en shmat(): ")	+ strerror(errno);
	return *this;
}

template<class T> int PilaCompartida<T>::cantidadProcesosAdosados() const
{
	shmid_ds estado;
	if (shmctl(this->shm_id, IPC_STAT, &estado) == -1)
		throw std::string("Error en shmctl(): ") + strerror(errno);
	return estado.shm_nattch;
}

template<class T> void PilaCompartida<T>::log(const std::string & mensaje)
{
	std::cout << "[" << getpid() << "] " << mensaje << std::endl;
}

template<class T>
key_t PilaCompartida<T>::generarClave(const std::string & archivo, char letra)
{
	key_t clave = ftok(archivo.c_str(), letra);
	if (clave == -1)
		throw std::string("Error al generar la clave.");
	return clave;
}

#endif /* PILACOMPARTIDA_H_ */
